﻿using System.Collections.Generic;
using System.Linq;
using Notoric.Commanding;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Implements an undoable command for beaming a group of durational symbols.
    /// </summary>
    public class MakeBeamGroupCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MakeBeamGroupCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo stack</param>
        /// <param name="redoStack">The redo stack</param>
        public MakeBeamGroupCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<Elements.CWNotation.Beam>("CreatedBeam", ParameterDirection.Out);
            AddParameter<IEnumerable<Elements.CWNotation.DurationalSymbol>>("Symbols", ParameterDirection.In);
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var beams = (from symbol in Symbols
                         where symbol.GroupBeam != null
                         select symbol.GroupBeam).Distinct();

            splitPrevBeamsCommand = new UndoableMacroCommand(UndoStack, RedoStack)
            {
                UseStacks = false
            };

            foreach (var b in beams)
                splitPrevBeamsCommand.Commands.Add(
                    new SplitBeamGroupCommand(UndoStack, RedoStack)
                    {
                        Symbols = b.BeamedSymbols
                    }
                    );
            splitPrevBeamsCommand.Execute(null);

            var firstChord = (Elements.CWNotation.Chord)Symbols.First();
            var stemDirection = firstChord.StemDirection;
            var toRevert = from symbol in Symbols
                           where symbol is Elements.CWNotation.Chord && ((Elements.CWNotation.Chord)symbol).StemDirection != stemDirection
                           select symbol as Elements.CWNotation.Chord;

            stemDirectionsCommand = new UndoableMacroCommand(UndoStack, RedoStack)
            {
                UseStacks = false
            };
            foreach (var chord in toRevert)
                stemDirectionsCommand.Commands.Add(chord.CreateSetStemDirectionCommand(stemDirection));
            stemDirectionsCommand.Execute(null);

            var modelSymbols = new List<Model.Elements.CWNotation.DurationalSymbol>(
                                    from symbol in Symbols
                                    select symbol.AdaptedElement as Model.Elements.CWNotation.DurationalSymbol
                                    );
            var modelBeam = Model.Elements.CWNotation.Beam.BeamGroup(modelSymbols);
            
            var modelStaff = modelBeam.FindOwner<Model.Elements.CWNotation.Staff>();
            var system = Symbols.First().FindOwner<Elements.CWNotation.System>();
            var staff = system.Staves.First(s => s.AdaptedElement == modelStaff);

            CreatedBeam = (Elements.CWNotation.Beam)staff.Attachments.First(a => a.AdaptedElement == modelBeam);
            CreatedBeam.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            splitPrevBeamsCommand.Execute(null);
            stemDirectionsCommand.Execute(null);
            
            var modelBeam = (Model.Elements.CWNotation.Beam)CreatedBeam.AdaptedElement;

            foreach (var symbol in modelBeam.BeamedSymbols)
                symbol.GroupBeam = modelBeam;

            var staff = CreatedBeam.FindOwner<Elements.CWNotation.Staff>();
            ((Model.Elements.CWNotation.Staff)staff.AdaptedElement).Attachments.Add(modelBeam);
            var beam = CreatedBeam.FindOwner<Elements.CWNotation.Staff>().Attachments.First(a => a.AdaptedElement == modelBeam);
            var index = staff.Attachments.IndexOf(beam);
            staff.Attachments[index] = CreatedBeam;
            
            CreatedBeam.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            foreach (var symbol in ((Model.Elements.CWNotation.Beam)CreatedBeam.AdaptedElement).BeamedSymbols)
                symbol.GroupBeam = null;
            ((Model.Elements.CWNotation.Staff)CreatedBeam.FindOwner<Elements.CWNotation.Staff>().AdaptedElement).Attachments.Remove(CreatedBeam.AdaptedElement as Model.Elements.CWNotation.Beam);

            stemDirectionsCommand.Execute(null);
            splitPrevBeamsCommand.Execute(null);

            CreatedBeam.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the beam created by this command.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Beam"/>
        /// </para>
        /// <para>
        /// Gets the beam created by this command.<br/>
        /// This property wraps the "CreatedBeam" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Beam CreatedBeam
        {
            get { return GetParameterValue<Elements.CWNotation.Beam>("CreatedBeam"); }
            private set { SetParameterValue<Elements.CWNotation.Beam>("CreatedBeam", value); }
        }

        /// <summary>
        /// Gets or sets the symbols that are to be connected by the <see cref="CreatedBeam"/>.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="IEnumerable{T}"/> of <see cref="Elements.CWNotation.DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The collection of symbols that are to be connected by the <see cref="CreatedBeam"/>.<br/>
        /// This property wraps the "Symbols" command parameter.
        /// </para>
        /// </value>
        public IEnumerable<Elements.CWNotation.DurationalSymbol> Symbols
        {
            get { return GetParameterValue<IEnumerable<Elements.CWNotation.DurationalSymbol>>("Symbols"); }
            set { SetParameterValue<IEnumerable<Elements.CWNotation.DurationalSymbol>>("Symbols", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the command that has split any previous beams.
        /// </summary>
        private UndoableMacroCommand splitPrevBeamsCommand;
        /// <summary>
        /// Holds the command that changes the stem direction of the notes.
        /// </summary>
        private UndoableMacroCommand stemDirectionsCommand;

        #endregion
    }
}
